using System;
using System.Collections.Generic;
using System.Web.Routing;
using RestUpMVC.Representation;
using RestUpMVC.Resource;

namespace RestUpMVC
{
    /// <summary>
    /// A singleton class that allows for the configuration of the RestUpMVC framework.
    /// </summary>
    public class RestUpMvcConfiguration
    {
        private static readonly RestUpMvcConfiguration _instance = new RestUpMvcConfiguration();

        static RestUpMvcConfiguration()
        {
            
        }

        private RestUpMvcConfiguration()
        {
            BaseUri = string.Empty;
            ResourceIdPropertyName = "id";
            RepresentationFactory = new RepresentationFactory();
            RegisterRepresentation(new JsonRepresentation());
            RegisterRepresentation(new XmlRepresentation());
        }

        /// <summary>
        /// Gets the only instance of this class.
        /// </summary>
        public static RestUpMvcConfiguration Instance
        {
            get { return _instance; }    
        }

        /// <summary>
        /// Gets the name of the property that is the unique ID of the resources.  This defaults to "id".  This is not case sensitive.
        /// </summary>
        public string ResourceIdPropertyName { get; private set; }

        /// <summary>
        /// Gets the base URI for all resources, e.g. if a resource was located at /api/resource this would return api.  Defaults to an empty string.
        /// </summary>
        public string BaseUri { get; private set; }

        public IRepresentationFactory RepresentationFactory { get; private set; }

        /// <summary>
        /// Sets the base BaseUri property.
        /// </summary>
        public RestUpMvcConfiguration SetBaseUri(string uri)
        {
            if (uri == null) throw new ArgumentNullException("uri");

            if (uri.StartsWith("/"))
            {
                uri = uri.Remove(0, 1);
            }

            if (uri.EndsWith("/"))
            {
                uri = uri.TrimEnd('/');
            }

            BaseUri = uri;
            return this;
        }

        /// <summary>
        /// Sets the ResourceIdPropertyName property.
        /// </summary>
        public RestUpMvcConfiguration SetResourceIdPropertyName(string name)
        {
            ResourceIdPropertyName = name;
            return this;
        }

        /// <summary>
        /// Registers a new resource to be exposed by the framework.
        /// For example, given the root URI for the application as /api, calling RegisterResource(typeof(Blog)) would create a route that maps the URI /api/blog to the BlogController class.
        /// </summary>
        /// <param name="resourceType">Type of the resource to be exposed.</param>
        /// <returns>The RestUpMvcConfiguration instance.</returns>
        public RestUpMvcConfiguration RegisterResource(Type resourceType)
        {
            return RegisterResource(resourceType, true);
        }

        /// <summary>
        /// Registers a new resource to be exposed by the framework.  Does not include the resource name in the URI that gets routed to the controller for this resource.
        /// For example, given the root URI for the application as /api, calling RegisterResource(typeof(Blog), false) would create a route that maps the URI /api/ to the BlogController class.
        /// </summary>
        /// <param name="resourceType">Type of the resource to be exposed.</param>
        /// <param name="includeNameInUri">Determines whether or not the name of the resource is included in the URI that gets routed to the controller for the resource.</param>
        /// <returns></returns>
        public RestUpMvcConfiguration RegisterResource(Type resourceType, bool includeNameInUri)
        {
            if (resourceType == null) throw new ArgumentNullException("resourceType");

            var metaData = new ResourceMetaData();
            ResolveResourceMetaData(resourceType, metaData);

            LinkedList<ResourceMetaData> parents = ResolveResourceParents(metaData);

            string baseUri = BuildBaseUri(parents);

            var routes = new ResourceRouteBuilder().BuildRoutes(baseUri, metaData, includeNameInUri);
            foreach (var routeBase in routes)
            {
                RouteTable.Routes.Add(routeBase);
            }

            return this;
        }

        private string BuildBaseUri(LinkedList<ResourceMetaData> parents)
        {
            string path = string.Empty;
            var aParent = parents.First;
            while (aParent != null)
            {
                path += aParent.Value.Name + "/{" + aParent.Value.Name + aParent.Value.IdentityPropertyName + "}/";
                aParent = aParent.Next;
            }
            
            path = BaseUri.Equals(string.Empty) ? path : BaseUri + "/" + path;
            path = path.TrimEnd('/');
            return path;
        }

        private static LinkedList<ResourceMetaData> ResolveResourceParents(ResourceMetaData metaData)
        {
            var parents = new LinkedList<ResourceMetaData>();
            var parentType = metaData.ParentResource;
            while (parentType != null)
            {
                var parentMeta = new ResourceMetaData();
                ResolveResourceMetaData(parentType, parentMeta);
                parents.AddFirst(parentMeta);
                parentType = parentMeta.ParentResource;
            }
            return parents;
        }

        private static void ResolveResourceMetaData(Type resourceType, ResourceMetaData metaData)
        {
            var resolvers = new IResourceMetaDataResolver[]
                                {
                                    new ResourceNameResolver(),
                                    new ResourceIdentityPropertyNameResolver(),
                                    new ResourceControllerResolver(),
                                    new ResourceParentResolver()
                                };

            foreach (var resourceMetaDataResolver in resolvers)
            {
                resourceMetaDataResolver.ResolveMetaData(resourceType, metaData);
            }
        }

        public void RegisterRepresentation(IRepresentation representation)
        {
            if (representation == null) throw new ArgumentNullException("representation");
            if (!RepresentationFactory.RepresentationForContentTypeExists(representation.ContentType))
            {
                RepresentationFactory.Add(representation);
                return;
            }
            
        }
    }
}